package com.tj.bo.account.server.services;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fqgj.base.services.redis.RedisClient;
import com.fqgj.common.api.Page;
import com.fqgj.common.utils.CollectionUtils;
import com.fqgj.common.utils.StringUtils;
import com.fqgj.exception.common.ApplicationException;
import com.google.common.collect.Lists;
import com.tj.bo.account.client.AccountBalanceChangeService;
import com.tj.bo.account.client.DepositService;
import com.tj.bo.account.client.enums.DepositStatusEnum;
import com.tj.bo.account.client.enums.IncomeEnum;
import com.tj.bo.account.client.vo.*;
import com.tj.bo.account.server.dao.AccountExternalDao;
import com.tj.bo.account.server.dao.OrderDepositDao;
import com.tj.bo.account.server.entity.AccountExternalEntity;
import com.tj.bo.account.server.entity.OrderDepositEntity;
import com.tj.bo.common.conf.config.RedisConfigUtil;
import com.tj.bo.common.conf.constant.RedisKey;
import com.tj.bo.common.conf.constant.RedisTimeConstant;
import com.tj.bo.common.conf.enums.PaymentChannelEnum;
import com.tj.bo.common.conf.util.SerialNumberUtil;
import com.tj.bo.integration.PaymentIntegrationService;
import com.tj.bo.integration.input.PaymentInput;
import com.tj.bo.integration.output.PaymentChannelOutput;
import com.tj.bo.payment.client.PayCommandService;
import com.tj.bo.payment.client.PayQueryService;
import com.tj.bo.payment.client.vo.PayApplicationVO;
import com.tj.bo.payment.client.vo.PaymentChannelVO;
import com.tj.bo.payment.client.vo.PaymentOrderVO;
import com.tj.bo.user.client.UserQueryService;
import com.tj.bo.user.client.UserRegisterService;
import com.tj.bo.user.client.vo.input.UserAccessUpdateRO;
import com.tj.bo.user.client.vo.output.UserAccessInfoRO;
import org.apache.commons.lang3.RandomUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 充值服务
 * User: anonymous
 * Date: 2020/06/11
 * Time: 下午2:16
 */
@Service
public class DepositServiceImpl implements DepositService {
    private static final Logger logger = LoggerFactory.getLogger(DepositServiceImpl.class);
    public static final String DEPOSIT_SUCCESS = "0000";
    @Autowired
    private OrderDepositDao orderDepositDao;
    @Autowired
    private PaymentIntegrationService paymentIntegrationService;
    @Autowired
    private RedisConfigUtil accountConfig;
    @Autowired
    private AccountBalanceChangeService accountBalanceChangeService;
    @Autowired
    private AccountExternalDao accountExternalDao;
    @Autowired
    private UserQueryService userQueryService;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RedisClient redisClient;
    @Autowired
    private UserRegisterService userRegisterService;
    @Autowired
    private PayQueryService payQueryService;
    @Autowired
    private PayCommandService payCommandService;

    /**
     * 获取充值通道列表
     */
    @Override
    public List<DepositChannelVO> getDepositChannelList(String userCode, boolean userInstaMojoPay) {
//        String payBaseUrl = accountConfig.getPayBaseUrl();
//        String productCategory = accountConfig.getProductCategory();
//        List<PaymentChannelOutput> paymentChannelList = paymentIntegrationService.getPaymentChannelList(payBaseUrl, productCategory);
//
//        boolean needAuthentication = userQueryService.needAuthentication(userCode);
//        if(!accountConfig.getUserAuthPageSwitch()){
//            needAuthentication = false;
//        }
//        return convertToDepositChannelVO(paymentChannelList, userCode, userInstaMojoPay, needAuthentication);
        List<PaymentChannelVO> list = payQueryService.getPaymentChannels();
        return list.stream()
                .map(this::convert)
                .collect(Collectors.toList());
    }

    /**
     * 获取充值通道
     */
    @Override
    public DepositChannelVO getDepositChannel(int channelCode, boolean userInstaMojoPay) {
//        List<DepositChannelVO> depositChannelList = getDepositChannelList("", userInstaMojoPay);
//        for (DepositChannelVO depositChannelVO : depositChannelList) {
//            if (depositChannelVO.getPayChannel() == channelCode) {
//                return depositChannelVO;
//            }
//        }
//        throw new ApplicationException("Please select channel");
        List<PaymentChannelVO> list = payQueryService.getPaymentChannels();
        return list.stream()
                .map(this::convert)
                .findFirst()
                .orElseGet(DepositChannelVO::new);
    }

    private DepositChannelVO convert(PaymentChannelVO paymentChannelVO){
        DepositChannelVO depositChannelVO = new DepositChannelVO();
        depositChannelVO.setTip(paymentChannelVO.getName());
        depositChannelVO.setPayChannelName(paymentChannelVO.getName());
        depositChannelVO.setPayChannel(paymentChannelVO.getCode());
        depositChannelVO.setServiceFeeRate(paymentChannelVO.getServerFeeRate());
        return depositChannelVO;
    }

    /**
     * 充值
     */
    @Override
    public Object deposit(DepositVO depositVO) {
        String depositOrderNo = SerialNumberUtil.getBizOrderNumber("D", "P");
        depositVO.setDepositOrderNo(depositOrderNo);
        depositVO.setDepositStatusEnum(DepositStatusEnum.PROCESSING);
        //创建充值订单
        OrderDepositEntity orderDepositEntity = createDepositOrder(depositVO);
        //调用支付接口
//        String payBaseUrl = accountConfig.getPayBaseUrl();
//        PaymentInput paymentInput = generatePaymentInput(depositVO, depositOrderNo);
//        PaymentOutput paymentOutput = paymentIntegrationService.payment(payBaseUrl, paymentInput);
        PayApplicationVO payApplicationVO = new PayApplicationVO();
        payApplicationVO.setPayAmount(depositVO.getAmount().intValue());
        payApplicationVO.setPayBankCode(String.valueOf(depositVO.getDepositChannelCode()));
        payApplicationVO.setProductName(depositVO.getDepositAmountCode());
        payApplicationVO.setAttach(orderDepositEntity.getDepositOrderNo());
        PaymentOrderVO paymentOrderVO = payCommandService.createPayOrder(payApplicationVO);
        //save accept_no
        orderDepositEntity.setAccountNo(paymentOrderVO.getOrderCode());
        orderDepositDao.updateByPrimaryKey(orderDepositEntity);
        return paymentOrderVO.getPayUrlWrapper().getHtml();
    }


    /**
     * 充值回调
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public DepositNotifyResultVO depositNotify(DepositNotifyVO depositNotifyVO) {
        OrderDepositEntity orderDepositEntity = orderDepositDao.selectDepositOrder(depositNotifyVO.getDepositOrderNo());
        //check amount
        checkAmount(depositNotifyVO, orderDepositEntity);
        if (!orderDepositEntity.getStatus().equals(DepositStatusEnum.PROCESSING.getCode())) {
            throw new ApplicationException("deposit status error");
        }
        //更改状态
        if (DEPOSIT_SUCCESS.equals(depositNotifyVO.getCode())) {
            orderDepositEntity.setPayAcceptNo(depositNotifyVO.getAcceptNo());
            orderDepositEntity.setStatus(DepositStatusEnum.SUCCESS.getCode());
            BigDecimal grants = orderDepositEntity.getGrants();

            RLock lock = redissonClient.getLock(String.format(RedisKey.BINARY_OPTION_FIRST_DEPOSIT_GRANTS, orderDepositEntity.getUserCode()));
            lock.lock(5, TimeUnit.SECONDS);
            try {
                Integer versionCode = StringUtils.isEmpty(depositNotifyVO.getAppVersion()) ? 0 : Integer.valueOf(depositNotifyVO.getAppVersion().replaceAll("\\.", ""));
                boolean hasDepositRecord = this.hasDepositSuccessRecord(orderDepositEntity.getUserCode());
                //新版本赠送首充金额
                if(!hasDepositRecord && versionCode > 109){
                    grants = grants.add(new BigDecimal(accountConfig.firstDepositGrants()));
                    orderDepositEntity.setGrants(grants);
                }
            } catch (Exception e) {
                logger.error("============= 第一次deposit赠送金额异常 e: ===========", e);
            } finally {
                lock.unlock();
            }

            orderDepositDao.updateByPrimaryKey(orderDepositEntity);
            //入账
            AccountIncomeVO accountIncomeVO = new AccountIncomeVO();
            accountIncomeVO.setUserCode(orderDepositEntity.getUserCode());
            accountIncomeVO.setOrderNo(orderDepositEntity.getDepositOrderNo());
            accountIncomeVO.setAmount(orderDepositEntity.getAmount());
            accountIncomeVO.setGrants(grants);
            accountIncomeVO.setIncomeEnum(IncomeEnum.DEPOSIT);
            boolean income = accountBalanceChangeService.income(accountIncomeVO);
            if (!income) {
                logger.error("depositNotify income error:depositNotifyVO:{},", JSON.toJSONString(depositNotifyVO));
                throw new ApplicationException("income error");
            }

            if(StringUtils.isNotEmpty(depositNotifyVO.getInfoOrder())){
                InfoOrderVO infoOrderVO = JSONObject.parseObject(depositNotifyVO.getInfoOrder(), InfoOrderVO.class);
                UserAccessUpdateRO userAccessUpdateRO = new UserAccessUpdateRO();
                userAccessUpdateRO.setUserCode(orderDepositEntity.getUserCode());
                userAccessUpdateRO.setEmail(infoOrderVO.getEmail());
                userAccessUpdateRO.setMobile(infoOrderVO.getMobile());
                userAccessUpdateRO.setName(infoOrderVO.getName());
                userRegisterService.updateUserAccess(userAccessUpdateRO);
                logger.info("============= 支付成功回调后 更新用户信息 infoOrder:{} =============", depositNotifyVO.getInfoOrder());
            }
        } else {
            orderDepositEntity.setStatus(DepositStatusEnum.FAILED.getCode());
            orderDepositDao.updateByPrimaryKey(orderDepositEntity);
        }

        DepositNotifyResultVO depositNotifyResult = new DepositNotifyResultVO();
        depositNotifyResult.setUserCode(orderDepositEntity.getUserCode());
        return depositNotifyResult;
    }

    /**
     * 获取最新一笔充值订单
     */
    @Override
    public DepositVO queryLatestDepositOrder(String userCode) {
        OrderDepositEntity depositEntity = orderDepositDao.selectLatestDepositOrder(userCode);
        return depositEntityToVO(depositEntity);
    }

    /**
     * 获取充值记录
     */
    @Override
    public List<DepositVO> getDepositRecordList(String userCode) {
        Page page = new Page(500);

        Map<String, Object> params = new HashMap<>();
        params.put("userCode", userCode);
        params.put("deleted", 0);
        params.put("page", page);
        params.put("orderBy", " id desc ");

        List<OrderDepositEntity> orderDepositEntities = orderDepositDao.selectByParams(params);
        List<DepositVO> depositVOList = new ArrayList<>();
        for (OrderDepositEntity orderDepositEntity : orderDepositEntities) {
            depositVOList.add(depositEntityToVO(orderDepositEntity));
        }
        return depositVOList;
    }

    @Override
    public int getDepositRecordListByUserCode(String userCode) {
        List<OrderDepositEntity> orderDepositEntities = orderDepositDao.selectDepositOrdersByUserCode(userCode);
        List<DepositVO> deposits = Lists.newArrayList();
        Integer orderNumbers = 0;
        if(CollectionUtils.isNotEmpty(orderDepositEntities)){
            for(OrderDepositEntity entity : orderDepositEntities){
                deposits.add(this.depositEntityToVO(entity));
            }
            orderNumbers = deposits.size();
        }
        return orderNumbers;
    }

    /**
     * 创建充值订单
     */
    private OrderDepositEntity createDepositOrder(DepositVO depositVO) {
        OrderDepositEntity orderDepositEntity = new OrderDepositEntity();
        BeanUtils.copyProperties(depositVO, orderDepositEntity);
        orderDepositEntity.setStatus(depositVO.getDepositStatusEnum().getCode());
        return orderDepositDao.insert(orderDepositEntity);
    }

    private DepositVO depositEntityToVO(OrderDepositEntity orderDepositEntity) {
        DepositVO depositVO = new DepositVO();
        BeanUtils.copyProperties(orderDepositEntity, depositVO);
        depositVO.setDepositStatusEnum(DepositStatusEnum.getDepositStatusByCode(orderDepositEntity.getStatus()));
        return depositVO;
    }

    private List<DepositChannelVO> convertToDepositChannelVO(List<PaymentChannelOutput> paymentChannelList, String userCode, boolean userInstaMojoPay, boolean needAuthentication) {
        List<DepositChannelVO> depositChannelVOList = new ArrayList<>();
        for (PaymentChannelOutput paymentChannelOutput : paymentChannelList) {
            //check white list
            if (!"all".equals(paymentChannelOutput.getUserCodeWhiteList()) && !paymentChannelOutput.getUserCodeWhiteList().contains(userCode)) {
                continue;
            }

            if(!userInstaMojoPay && paymentChannelOutput.getChannelCode() == PaymentChannelEnum.INSTAMOJO.getCode()){
                continue;
            }

            DepositChannelVO depositChannelVO = new DepositChannelVO();
            depositChannelVO.setPayChannel(paymentChannelOutput.getChannelCode());
            depositChannelVO.setPayChannelName(paymentChannelOutput.getNameInApp());
            depositChannelVO.setServiceFee(paymentChannelOutput.getChannelServiceFee());
            depositChannelVO.setServiceFeeRate(null);
            depositChannelVO.setIconUrl(paymentChannelOutput.getIcon());
            depositChannelVO.setTip("");
            if(paymentChannelOutput.getChannelCode() == PaymentChannelEnum.INSTAMOJO.getCode()){
                depositChannelVO.setNeedAuthentication(needAuthentication);
                depositChannelVO.setPayChannelName("Pay");
            }
            depositChannelVOList.add(depositChannelVO);
        }
        return depositChannelVOList;
    }

    private PaymentInput generatePaymentInput(DepositVO depositVO, String depositOrderNo) {

        UserAccessInfoRO userAccessInfo = userQueryService.getUserAccessInfoByUserCode(depositVO.getUserCode());
        String email = userAccessInfo.getEmail();
        String userName = userAccessInfo.getUserName();
        String mobile = userAccessInfo.getMobile();
        Integer needThreeElements = 0;
        if(userAccessInfo.getIsAuthentication() == 1){
            needThreeElements = 1;
        }

        AccountExternalEntity accountExternalEntity = accountExternalDao.selectExternalAccount(depositVO.getUserCode());
        email = StringUtils.isNotEmpty(email) ? email : ((accountExternalEntity != null && StringUtils.isNotEmpty(accountExternalEntity.getUserEmail())) ? accountExternalEntity.getUserEmail() : "abctradecustomer@gmail.com");
        userName = StringUtils.isNotEmpty(userName) ? userName : ((accountExternalEntity != null && StringUtils.isNotEmpty(accountExternalEntity.getUserName())) ? accountExternalEntity.getUserName() : "AbcTradeCustomer");
        mobile = StringUtils.isNotEmpty(mobile) ? mobile : String.valueOf(RandomUtils.nextLong(9000000000L, 9999999999L));

        PaymentInput paymentInput = new PaymentInput();
        paymentInput.setProductCategoryCode(accountConfig.getProductCategory());
        paymentInput.setUserCode(depositVO.getUserCode());
        paymentInput.setAmount(depositVO.getAmount().toString());
        paymentInput.setNotifyUrl(accountConfig.getDomainUrl() + "deposit/notify");
        paymentInput.setEmail(email);
        paymentInput.setBizNo(depositOrderNo);
        paymentInput.setAcctName(userName);
        paymentInput.setMobile(mobile);
        paymentInput.setChannelCode(depositVO.getDepositChannelCode());
        paymentInput.setRepaymentType(accountConfig.getRazorpayType());
        if(depositVO.getDepositChannelCode() == PaymentChannelEnum.INSTAMOJO.getCode()){
            paymentInput.setRepaymentType(accountConfig.getInstaMojoType());
        }
        paymentInput.setAppVersion(depositVO.getAppVersion());
        paymentInput.setNeedThreeElements(needThreeElements);

        return paymentInput;
    }

    private void checkAmount(DepositNotifyVO depositNotifyVO, OrderDepositEntity orderDepositEntity) {
        boolean inconsistent = depositNotifyVO.getAcceptAmount().compareTo(orderDepositEntity.getAmount()) != 0;
        if (inconsistent) {
            logger.error("depositNotify inconsistent error:depositNotifyVO:{},OrderDepositEntityAmount:{}", JSON.toJSONString(depositNotifyVO), orderDepositEntity.getAmount());
            throw new ApplicationException("inconsistent error");
        }
    }

    @Override
    public UserDepositVO getUserDeposit(String userCode) {
        List<OrderDepositEntity> orderDepositEntities = orderDepositDao.selectDepositOrdersByUserCode(userCode);
        UserDepositVO depositVO = new UserDepositVO();
        if (CollectionUtils.isNotEmpty(orderDepositEntities)) {
            BigDecimal totalDepositAmount = BigDecimal.ZERO;
            for (OrderDepositEntity entity : orderDepositEntities) {
                totalDepositAmount = totalDepositAmount.add(entity.getAmount());
            }
            depositVO.setTotalDepositAmount(totalDepositAmount);
        }
        return depositVO;
    }

    @Override
    public UserDepositVO getUserDepositByOrderNo(String depositOrderNo) {
        OrderDepositEntity orderDepositEntity = orderDepositDao.selectDepositOrder(depositOrderNo);
        UserDepositVO depositVO = null;
        if(orderDepositEntity != null){
            depositVO = new UserDepositVO();
            BeanUtils.copyProperties(orderDepositEntity, depositVO);
        }
        return depositVO;
    }

    @Override
    public boolean needAuthentication(String userCode) {

        return false;
    }

    @Override
    public boolean hasDepositSuccessRecord(String userCode) {

        if(redisClient.get(RedisKey.BINARY_OPTION_HAS_DEPOSIT_RECORD, userCode) != null){
            return true;
        }
        List<DepositVO> depositRecordList = this.getDepositRecordList(userCode);
        if(CollectionUtils.isNotEmpty(depositRecordList)){
            for(DepositVO depositVO : depositRecordList){
                if(Objects.equals(depositVO.getDepositStatusEnum(), DepositStatusEnum.SUCCESS)){
                    redisClient.set(RedisKey.BINARY_OPTION_HAS_DEPOSIT_RECORD, userCode, RedisTimeConstant.ONE_MONTH, userCode);
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public List<DepositVO> selectsDepositOrderListByStatusAndTime(Integer orderStatus, Date endTime) {
        List<OrderDepositEntity> orderDepositEntities = orderDepositDao.selectsDepositOrderListByStatusAndTime(orderStatus, endTime);
        List<DepositVO> deposits = Lists.newArrayList();
        if(CollectionUtils.isNotEmpty(deposits)){
            for(OrderDepositEntity entity : orderDepositEntities){
                deposits.add(depositEntityToVO(entity));
            }
        }
        return deposits;
    }

    @Override
    public void updateDepositStatus(String orderNo, Integer status) {

    }

    @Override
    public BigDecimal selectTotalDepositAmount(String userCode) {
        return orderDepositDao.selectTotalDepositAmount(userCode);
    }
}